﻿namespace Neovolve.Extensibility.ReSharper
{
    using System;
    using JetBrains.Application;
    using JetBrains.ProjectModel;
    using JetBrains.ReSharper.Psi;
    using JetBrains.ReSharper.Psi.Caches;
    using JetBrains.ReSharper.Psi.CSharp.Impl;
    using JetBrains.ReSharper.Psi.CSharp.Tree;
    using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
    using JetBrains.ReSharper.Psi.Tree;
    using JetBrains.TextControl;

    /// <summary>
    /// The <see cref="ConvertTypeBulbItem"/>
    ///   class is used to convert type references between alias and Clr type names.
    /// </summary>
    internal class ConvertTypeBulbItem : IConvertTypeBulbItem
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ConvertTypeBulbItem"/> class.
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        public ConvertTypeBulbItem(NodeDescriptor descriptor)
        {
            if (descriptor == null)
            {
                throw new ArgumentNullException("descriptor");
            }

            Descriptor = descriptor;
        }

        /// <summary>
        /// Executes the specified solution.
        /// </summary>
        /// <param name="solution">
        /// The solution.
        /// </param>
        /// <param name="textControl">
        /// The text control.
        /// </param>
        public void Execute(ISolution solution, ITextControl textControl)
        {
            ITreeNode newNode;

            if (Descriptor.IsClrType)
            {
                newNode = Descriptor.CreateAliasTypeNode();
            }
            else
            {
                INamespace ns = CreateNamespace(Descriptor, "System");

                EnsureNamespaceExists(Descriptor, ns);

                newNode = Descriptor.CreateClrTypeNode();
            }

            ReplaceNode(newNode);
        }

        /// <summary>
        /// Creates the namespace.
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        /// <param name="qualifiedNamespaceName">
        /// Name of the qualified namespace.
        /// </param>
        /// <returns>
        /// A <see cref="INamespace"/> instance.
        /// </returns>
        private INamespace CreateNamespace(NodeDescriptor descriptor, String qualifiedNamespaceName)
        {
            IProjectFile projectFile = Descriptor.Node.GetProjectFile();

            if (projectFile == null)
            {
                return null;
            }

            ICSharpFile file = (ICSharpFile)descriptor.Node.GetContainingFile();

            if (file == null)
            {
                return null;
            }

            ISolution solution = descriptor.Node.GetSolution();
            PsiManager psiManager = PsiManager.GetInstance(solution);
            IPsiModule module = descriptor.Node.GetPsiModule();
            IDeclarationsScope scope = DeclarationsScopeFactory.ModuleScope(module, true);
            IDeclarationsCache cache = psiManager.GetDeclarationsCache(scope, true);
            INamespace ns = cache.GetNamespace(qualifiedNamespaceName);

            return ns;
        }

        /// <summary>
        /// Ensures the namespace exists.
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        /// <param name="ns">
        /// The namespace to check.
        /// </param>
        private void EnsureNamespaceExists(NodeDescriptor descriptor, INamespace ns)
        {
            ICSharpTypeAndNamespaceHolderDeclarationNode declarationNode =
                descriptor.Node.GetContainingElement<ICSharpTypeAndNamespaceHolderDeclarationNode>(false);
            ICSharpTypeAndNamespaceHolderDeclaration declarationNodeToUpdate = null;

            if (declarationNode != null)
            {
                if (UsingUtil.CheckAlreadyImported(declarationNode, ns))
                {
                    // The namespace is already imported at the namespace scope
                    return;
                }

                if (declarationNode.Imports.Count > 0)
                {
                    declarationNodeToUpdate = declarationNode;
                }
            }

            IProjectFile projectFile = Descriptor.Node.GetProjectFile();

            if (projectFile == null)
            {
                return;
            }

            ICSharpFile file = (ICSharpFile)descriptor.Node.GetContainingFile();

            if (file == null)
            {
                return;
            }

            if (UsingUtil.CheckAlreadyImported(file, ns))
            {
                // The namespace is already imported at the file scope
                return;
            }

            // We need to add the namespace
            // Create the new using statement

            // If the declation node has not be identified as the namespace scope, use the file as the secondary option
            if (declarationNodeToUpdate == null)
            {
                declarationNodeToUpdate = file;
            }

            // Add it to the file
            UsingUtil.AddImportTo(declarationNodeToUpdate, ns);
        }

        /// <summary>
        /// Replaces the node.
        /// </summary>
        /// <param name="newNode">
        /// The new node.
        /// </param>
        private void ReplaceNode(ITreeNode newNode)
        {
            // replace AST fragment in actual file
            using (WriteLockCookie.Create())
            {
                ModificationUtil.ReplaceChild(Descriptor.Node, newNode);
            }
        }

        /// <summary>
        /// Gets the descriptor.
        /// </summary>
        /// <value>
        /// The descriptor.
        /// </value>
        public NodeDescriptor Descriptor
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the text of the item.
        /// </summary>
        /// <value>
        /// The text of the item.
        /// </value>
        public String Text
        {
            get
            {
                if (Descriptor.IsClrType)
                {
                    return "Convert " + Descriptor.TypeMapping.ClrTypeName + " to " + Descriptor.TypeMapping.AliasTypeName;
                }

                return "Convert " + Descriptor.TypeMapping.AliasTypeName + " to " + Descriptor.TypeMapping.ClrTypeName;
            }
        }
    }
}